extern "C" {
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <signal.h>
}
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cstring>
#include <map>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>

#include "base.hpp"
#include "connection.hpp"
#define CONST_DEFINED
#include "http.hpp"
#include "http_request.hpp"
#include "logger.hpp"
#include "utils.hpp"
#include "mime_type.h"
#include "http_request.hpp"
#include "cgi.hpp"  // TODO mod_cgi

// 定数多いな
static const char HTTP_V[] = "HTTP/1.1";
static const char CRLF[] = "\r\n";
static const char CRLFCRLF[] = "\r\n\r\n";
static const char S101[] = "101 Switching protocols";
static const char S200[] = "200 OK";
static const char S304[] = "304 Not modified";
static const char S400[] = "400 Bad request";
static const char S403[] = "403 Forbidden";
static const char S404[] = "404 Not found";
static const char S406[] = "406 Not acceptable";
static const char S412[] = "412 Precondition Failed";
static const char S417[] = "417 Expectation failed";
static const char S500[] = "500 Internal Server Error";
static const char S501[] = "501 Not implemented";

struct unsupported_header {
  const char* header;
  const char* status;
};
static const unsupported_header EXPECT = {"Expect", S417};
static const unsupported_header* UNSUPPORTEDs[] = { &EXPECT, NULL };

// functions...
namespace http {

  static bool make_response(Connection& conn);
  static void send_response(Connection& conn);

  void run(const int socket, const std::map<std::string, std::string>& env) {
    Connection conn(socket, env);
    while (true) {
      if (recv_request(conn) != STATE_REQUEST_END) {
        break;
      }
      if (!make_response(conn)) {
        break;
      }
      if (conn.req_contains_close()
          || conn.res_contains_close()) {
        break;
      }
      send_response(conn);
      conn.clear_map();
    }
  }

  static bool make_response(Connection& conn) {
    return true;
  }

  static bool bad_request(const Connection& conn) {
    if (!conn.req_has("Host")) {
      conn.send_http_status(HTTP_V, S400);
      return true;
    }
    return false;
  }

  static bool catch_unsupported_header(const Connection& conn) {
    for (int i=0; UNSUPPORTEDs[i] != NULL; ++i) {
      const unsupported_header* un = UNSUPPORTEDs[i];
      if (conn.req_has(un->header)) {
        conn.send_http_status(HTTP_V, un->status);
        return true;
      }
    }
    return false;
  }

  // 対応メソッド
  enum Method {
    GET, HEAD, UNKNOWN, // TODO
  };

  static Method get_method(const char* method) {
    if (method == NULL) {
      return UNKNOWN;
    }
    if (strcmp(method, "GET") == 0) {
      return GET;
    } else if (strcmp(method, "HEAD") == 0) {
      return HEAD;
    }
    return UNKNOWN;
  }

  static void send_response_GET_or_HEAD(Connection& conn, bool isGET=true);

  static void send_response(Connection& conn) {
    if (bad_request(conn)) {
      return;
    }
    if (catch_unsupported_header(conn)) {
      return;
    }
    switch (get_method(conn.get_req("Method"))) {
      case GET:
        send_response_GET_or_HEAD(conn);
        break;
      case HEAD:
        send_response_GET_or_HEAD(conn, false);
        break;
      default:
        conn.send_http_status(HTTP_V, S501);
    }
  }

  static bool send_response_304(Connection& conn, const time_t& mtime) {
    const char* val = conn.get_req("If-Modified-Since");
    if (val != NULL) {
      const std::string since(val);
      const std::string last_modifed = util::time::get_date_str_rfc1123(mtime);
      if (last_modifed == since) {
        conn.send_http_status(HTTP_V, S304);  // 完全一致
        return true;
      } else {
        const time_t since_t = util::time::parse_rfc1123(since.c_str(), 0);  // 夏時間無効
        const time_t lm_t = util::time::parse_rfc1123(last_modifed.c_str(), 0);
        if (lm_t <= since_t) {
          conn.send_http_status(HTTP_V, S304);
          return true;
        }
      }
    }
    return false;
  }
  static bool send_response_412(Connection& conn, const time_t& mtime) {
    const char* val = conn.get_req("If-Unmodified-Since");
    if (val == NULL) {
      return false;
    }
    const std::string since(val); // TODO
    const std::string last_modifed = util::time::get_date_str_rfc1123(mtime);
    if (last_modifed == since) {
      conn.send_http_status(HTTP_V, S412);
      return true;
    } else {
      const time_t since_t = util::time::parse_rfc1123(since.c_str(), 0);
      const time_t lm_t    = util::time::parse_rfc1123(last_modifed.c_str(), 0);
      if (since_t <= lm_t) {
        conn.send_http_status(HTTP_V, S412);
        return true;
      }
    }
    return false;
  }

  static void send_directory(Connection& conn);
  static void send_file(Connection& conn);

  static void send_response_GET_or_HEAD(Connection& conn, bool isGET) {
    const std::string request_uri = conn.get_req("Request-URI");
    const std::string path        = conn.get_env("DOC_ROOT") + request_uri;

    struct stat st; ::memset(&st, 0, sizeof(st));
    if (::stat(path.c_str(), &st) < 0) {
      logger::log("warn", __FILE__, __LINE__, "stat error: %s, path=%s",
          strerror(errno), path.c_str());
      conn.send_http_status(HTTP_V, S404);
      return;
    }

    // CGI スクリプト実行
    if (cgi::is_cgi(path.c_str())) {
      conn.set_req("PATH_TRANSLATED", path.c_str());
      cgi::run(conn);
      return;
    }
    // If-Unmodifed-Since
    if (send_response_412(conn, st.st_mtime)) {
      return;
    }
    // If-Modified-Since
    if (send_response_304(conn, st.st_mtime)) {
      return;
    }
    // HEAD なら以下不要
    if (!isGET) {
      return;
    }
    if (S_ISDIR(st.st_mode)) {
      conn.set_physical_file(path, st);
      send_directory(conn);
    } else {
      conn.set_physical_file(path, st);
      send_file(conn);
    }
  }

  static std::ostream& output_directory(std::ostream& out, DIR* dir);

  static void send_directory(Connection& conn) {
    const char* path = conn.get_file_path();
    DIR* dir = opendir(path);
    if (dir == NULL) {
      logger::log("warn", __FILE__, __LINE__, "opendir error: %s", strerror(errno));
      conn.send_http_status(HTTP_V, S404);  // なかったことにする
      return;
    }
    std::ostringstream content;
    content << "<html><head><title>" << path << "</title></head><body>";
    output_directory(content, dir);
    content << "</body></html>";
    const std::string& content_str = content.str();
    time_t lm;
    time(&lm);
    conn.set_res("Content-Type", "text/html");
    conn.set_res("Content-Length", content_str.size());
    conn.set_res("Last-Modified", util::time::get_date_str_rfc1123(lm));
    conn.set_res("Date", util::time::get_date_str_rfc1123());
    conn.send_status_line(HTTP_V, S200);
    conn.send_header();
    conn.send(content_str.c_str(), content_str.size());
  }

  static std::ostream& output_directory(std::ostream& out, DIR* dir) {
    out << "<ul style='list-style: none;'>";
    {
      std::vector<std::string> dirs;
      std::vector<std::string> files;
      dirent* dirent = NULL;
      while ((dirent = readdir(dir)) != NULL) {
        const char* name = dirent->d_name;
        if (strcmp(name, ".") == 0) {
          continue;
        }
        struct stat st;
        if (::stat(name, &st) == 0) {
          if (S_ISDIR(st.st_mode)) {
            dirs.push_back(name);
          } else if (S_ISREG(st.st_mode)) {
            files.push_back(name);
          }
        }
      }
      std::sort(dirs.begin(), dirs.end());
      std::sort(files.begin(), files.end());
      typedef std::vector<std::string>::iterator vs_iter;
      for (vs_iter it=dirs.begin(); it!=dirs.end(); ++it) {
        out << "<li><a href='" << *it << "/'>" << *it << "/" << "</a></li>";
      }
      for (vs_iter it=files.begin(); it!=files.end(); ++it) {
        out << "<li><a href='" << *it << "'>" << *it << "</a></li>";
      }
    }
    return out << "</ul>";
  }

  class fd_closer {
    const int fd;
  public:
    fd_closer(int d) : fd(d) {}
    ~fd_closer() { close(fd); }
  };

  static void send_file(Connection& conn) {
    const char* path = conn.get_file_path();
    const struct stat& st = conn.get_file_stat();

    const std::string& mimetype = get_mime_type(path);
    const std::string& charset  = get_charset_str(mimetype);
    conn.set_res("Content-Type", mimetype + "; " + charset);
    conn.set_res("Content-Length", st.st_size);
    conn.set_res("Last-Modified", util::time::get_date_str_rfc1123(st.st_mtime));
    conn.set_res("Date", util::time::get_date_str_rfc1123());
    conn.send_status_line(HTTP_V, S200);
    conn.send_header();

    int fd = open(path, O_RDONLY);
    if (fd == -1) {
      switch (errno) {
        case EACCES:
          conn.send_http_status(HTTP_V, S404);
          break;
        default:
          conn.send_http_status(HTTP_V, S500);
      }
      logger::log("warn", __FILE__, __LINE__, "open error: %s", strerror(errno));
      return;
    }
    fd_closer __fd(fd); // RAII
    const int acc = conn.c_socket();
    if (sendfile(acc, fd, NULL, st.st_size) == -1) {
      logger::log("warn", __FILE__, __LINE__, "sendfile error: %s", strerror(errno));
      return;
    }
  }

};
